// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 此文件包含用于调用解析器的导出入口点。

package parser

import (
	"bytes"
	"errors"
	"go/ast"
	"go/token"
	"io"
	"io/fs"
	"os"
	"path/filepath"
	"strings"
)

// 如果src！=nil，如果可能，readSource将src转换为[]字节；
// 否则返回错误。如果src==nil，readSource返回
// 读取文件名指定的文件的结果。
// 
func readSource(filename string, src any) ([]byte, error) {
	if src != nil {
		switch s := src.(type) {
		case string:
			return []byte(s), nil
		case []byte:
			return s, nil
		case *bytes.Buffer:
			// 是io。读卡器，但src已经以[]字节形式
			if s != nil {
				return s.Bytes(), nil
			}
		case io.Reader:
			return io.ReadAll(s)
		}
		return nil, errors.New("invalid source")
	}
	return os.ReadFile(filename)
}

// 模式值是一组标志（或0）。
// 它们控制解析的源代码量和其他可选的
// 解析器功能。
// 
type Mode uint

const (
	PackageClauseOnly    Mode             = 1 << iota // 在包子句后停止解析
	ImportsOnly                                       // 在导入声明后停止解析
	ParseComments                                     // 解析注释并将其添加到AST 
	Trace                                             // 打印解析产品的跟踪
	DeclarationErrors                                 // 报告声明错误
	SpuriousErrors                                    // 与所有错误相同，有关向后兼容性
	SkipObjectResolution                              // 不要将标识符解析为对象-请参阅ParseFile 
	AllErrors            = SpuriousErrors             // 报告所有错误（不只是不同行中的前10个错误）
)

// ParseFile解析单个Go源文件的源代码并返回
// 相应的ast。文件节点。源代码可以通过
// 源文件的文件名或通过src参数提供。
// 
// 如果src！=nil，ParseFile从src解析源代码，文件名为
// 仅在记录位置信息时使用。src参数的参数
// 的类型必须是字符串、[]字节或io。读者
// 如果src==nil，ParseFile将解析文件名指定的文件。
// 
// 模式参数控制解析的源文本量和其他
// 可选的解析器功能。如果设置了SkipObjectResolution mode位，
// 将跳过解析的对象解析阶段，导致文件丢失。作用域，
// 文件。未解决，全部识别。Obj字段为零。
// 
// 位置信息记录在文件集fset中，不能为
// nil。
// 
// 如果无法读取源代码，则返回的AST为零，错误
// 表示具体失败。如果读取了源代码，但发现语法
// 错误，则结果是部分AST（AST.Bad*节点
// 表示错误源代码的片段）。多个错误
// 通过扫描仪返回。按源位置排序的错误列表。
// 
func ParseFile(fset *token.FileSet, filename string, src any, mode Mode) (f *ast.File, err error) {
	if fset == nil {
		panic("parser.ParseFile: no token.FileSet provided (fset == nil)")
	}

	// get source 
	text, err := readSource(filename, src)
	if err != nil {
		return nil, err
	}

	var p parser
	defer func() {
		if e := recover(); e != nil {
			// 如果不是救援，恢复同样的恐慌
			if _, ok := e.(bailout); !ok {
				panic(e)
			}
		}

		// 设置结果值
		if f == nil {
			// 源文件不是有效的Go源文件-满足
			// 解析文件API并返回有效（但）空的
			// /*ast。File 
			f = &ast.File{
				Name:  new(ast.Ident),
				Scope: ast.NewScope(nil),
			}
		}

		p.errors.Sort()
		err = p.errors.Err()
	}()

	// parse source 
	p.init(fset, filename, text, mode)
	f = p.parseFile()

	return
}

// ParseDir为路径指定的
// 目录中名称以“.go”结尾的所有文件调用ParseFile，并返回package name->package 
// AST和找到的所有包的映射。
// 
// If过滤器！=无，只有带有fs的文件。将考虑通过
// 过滤器（以“.go”结尾）的FileInfo条目。模式位被传递到解析文件
// 不变。位置信息记录在fset中，
// 不能为零。
// 
// 如果无法读取目录，则返回一个nil映射和相应的错误
// 。如果发生解析错误，则返回一个非nil但不完整的映射和遇到的第一个错误
// 。
// 
func ParseDir(fset *token.FileSet, path string, filter func(fs.FileInfo) bool, mode Mode) (pkgs map[string]*ast.Package, first error) {
	list, err := os.ReadDir(path)
	if err != nil {
		return nil, err
	}

	pkgs = make(map[string]*ast.Package)
	for _, d := range list {
		if d.IsDir() || !strings.HasSuffix(d.Name(), ".go") {
			continue
		}
		if filter != nil {
			info, err := d.Info()
			if err != nil {
				return nil, err
			}
			if !filter(info) {
				continue
			}
		}
		filename := filepath.Join(path, d.Name())
		if src, err := ParseFile(fset, filename, nil, mode); err == nil {
			name := src.Name.Name
			pkg, found := pkgs[name]
			if !found {
				pkg = &ast.Package{
					Name:  name,
					Files: make(map[string]*ast.File),
				}
				pkgs[name] = pkg
			}
			pkg.Files[filename] = src
		} else if first == nil {
			first = err
		}
	}

	return
}

// ParseExprFrom是解析表达式的便利函数。
// 参数的含义与ParseFile相同，但源必须是有效的Go（类型或值）表达式。具体来说，fset不能为零。
// 
// 如果无法读取源代码，则返回的AST为零，错误
// 表示具体失败。如果读取了源代码，但发现语法
// 错误，则结果是部分AST（AST.Bad*节点
// 表示错误源代码的片段）。通过扫描仪返回多个错误
// 。按源位置排序的错误列表。
// 
func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (expr ast.Expr, err error) {
	if fset == nil {
		panic("parser.ParseExprFrom: no token.FileSet provided (fset == nil)")
	}

	// 获取来源
	text, err := readSource(filename, src)
	if err != nil {
		return nil, err
	}

	var p parser
	defer func() {
		if e := recover(); e != nil {
			// 如果不是救援，恢复同样的恐慌
			if _, ok := e.(bailout); !ok {
				panic(e)
			}
		}
		p.errors.Sort()
		err = p.errors.Err()
	}()

	// parse expr 
	p.init(fset, filename, text, mode)
	expr = p.parseRhsOrType()

	// 如果插入了分号，则使用它；huo jian defg
	if p.tok == token.SEMICOLON && p.lit == "\n" {
		p.next()
	}
	p.expect(token.EOF)

	return
}

// ParseExpr是一个方便的函数，用于获取表达式x的AST。
// AST中记录的位置信息未定义。错误消息中使用的
// 文件名为空字符串。
// 
// 如果发现语法错误，结果是部分AST（AST.Bad*节点
// 表示错误源代码的片段）。
// 通过扫描仪返回了多个错误。按源位置排序的错误列表。
// 
func ParseExpr(x string) (ast.Expr, error) {
	return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0)
}
